package com.liu.netty.demo.client;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.bytes.ByteArrayDecoder;
import io.netty.handler.codec.bytes.ByteArrayEncoder;
import io.netty.handler.timeout.IdleStateHandler;
import io.netty.util.HashedWheelTimer;
import lombok.*;
import lombok.extern.slf4j.Slf4j;

import java.nio.ByteOrder;
import java.util.Random;
import java.util.concurrent.TimeUnit;

/**
 * @program: https://www.joinsunsoft.com
 * @description:
 * @author: LiuYong
 * @email: david.liu@ginghan.com
 * @create: 2022/3/23 09:49
 */


@Slf4j
public abstract  class DemoClient {

    @Setter
    @Getter
    private String address;

    @Setter
    @Getter
    private int port;

    public DemoClient(String address, int port){
        this.address = address;
        this.port = port;
    }

    @Getter
    private Bootstrap bootstrap;
    @Getter
    private EventLoopGroup worker;

    public void start(){
        bootstrap = new Bootstrap();
        worker = new NioEventLoopGroup();
        ChannelFuture future = null;
        HashedWheelTimer timer = new HashedWheelTimer();

        try{
            bootstrap.group(worker);
            bootstrap.remoteAddress(address, port);
            bootstrap.channel(NioSocketChannel.class);

            //bootstrap.option(ChannelOption.SO_BACKLOG, 2048);//serverSocketchannel的设置，链接缓冲池的大小
            bootstrap.option(ChannelOption.SO_KEEPALIVE, true);//socketchannel的设置,维持链接的活跃，清除死链接
            bootstrap.option(ChannelOption.TCP_NODELAY, true);//socketchannel的设置,关闭延迟发送

            ClientConnectionInfo info = new ClientConnectionInfo();
            info.setWork(worker);

            bootstrap.handler(new ChannelInitializer<Channel>() {
                //初始化channel
                @Override
                protected void initChannel(Channel ch) throws Exception {

                    ch.pipeline().addLast(new IdleStateHandler(30,0, 60));
                    ch.pipeline().addLast(new ConnectionWatchdog(bootstrap, info, 3, 10));
                    ch.pipeline().addLast(initHandlers());
                }
            });

            doConnect(bootstrap);

            worker.terminationFuture().sync();

        }catch (Exception e){
            e.printStackTrace();
        }finally {

            log.error("Exit ClientDemo");

            try{
                worker.shutdownGracefully().sync();
            }catch (Exception t1){
                t1.printStackTrace();
            }
        }
    }

    protected abstract ChannelHandler[] initHandlers();

    private void doConnect(Bootstrap bootstrap){
        try {

            ChannelFuture future = bootstrap.connect();
            future.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture future) throws Exception {
                    if (future.isSuccess()) {
                        log.info("Connect server {}[:{}] is  started !!!", address, port);
                    } else {
                        if (future.cause() != null) {
                            log.error("Connect server {}[:{}] is failed as {}", future.cause().getMessage());
                        }

                        log.info("Client will re-connect in 30 seconds.", address, port);

                        future.channel().eventLoop().schedule(new Runnable() {
                            @Override
                            public void run() {
                                doConnect(bootstrap);
                            }
                        }, 30, TimeUnit.SECONDS);
                    }
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args){
        DemoClient dc = new DemoClient("localhost", 6666){

            @Override
            protected ChannelHandler[] initHandlers() {
                return new ChannelHandler[]{
                        new LengthFieldBasedFrameDecoder(ByteOrder.BIG_ENDIAN,
                                655360,0,
                                2,0,0,true),
                        new ByteArrayDecoder(),
                        new ByteArrayEncoder(),
                        new ClientByteArrayDemoChannelHandler("Client-" + new Random(3000).nextInt(3000))
                };
            }
        };

//        DemoClient dc = new DemoClient("localhost", 8080){
//
//            @Override
//            protected ChannelHandler[] initHandlers() {
//                return new ChannelHandler[]{
//                        new StringEncoder(),
//                        new StringDecoder(),
//                        new ClientDemoChannelHandler("Client-" + new Random(3000).nextInt(3000))
//                };
//            }
//        };

        dc.start();
    }

    @Data
    @NoArgsConstructor
    public static class ClientConnectionInfo{
        private int attempts = 0;
        private EventLoopGroup work;

        public void resetAttempts(){
            attempts = 0 ;
        }

        public void add(){
            attempts = attempts + 1;
        }
    }
}
